﻿using Extensions;
using Org.LLRP.LTK.LLRPV1;
using Org.LLRP.LTK.LLRPV1.DataType;
using Org.LLRP.LTK.LLRPV1.Impinj;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;

namespace Karbel.RFID
{
    public partial class DeviceManager
    {
        List<LLRPClient> readers = new List<LLRPClient>();

        private TagManager TagManager;
        private SynchronizationContext context = SynchronizationContext.Current ?? new SynchronizationContext();

        public DeviceManager(TagManager tagManager, ENUM_ImpinjInventorySearchType searchType, params string[] IpList)
        {
            TagManager = tagManager;

            foreach (string pair in IpList)
            {
                string[] pairs = pair.Split(',');
                string ipAddress = pairs[0];
                string[] powers = pairs[1].Split(':');
                ushort power = ushort.Parse(powers[0]);
                ushort sensitivity = ushort.Parse(powers[1]);
                StartReader(ipAddress, searchType, power, sensitivity);
            }
        }

        private void StartReader(string ip, ENUM_ImpinjInventorySearchType searchType, ushort power, ushort sensitivity)
        {
            LLRPClient reader = new LLRPClient();
            try
            {
                #region Initializing
                {
                    readers.Add(reader);
                    Impinj_Installer.Install();
                }
                #endregion

                #region EventHandlers
                {
                    //reader.OnReaderEventNotification += new delegateReaderEventNotification(reader_OnReaderEventNotification);                    
                    reader.OnRoAccessReportReceived += new delegateRoAccessReport(reader_OnRoAccessReportReceived);
                }
                #endregion

                #region Connecting
                {
                    ENUM_ConnectionAttemptStatusType status;
                    reader.Open(ip, 5000, out status);

                    if (status != ENUM_ConnectionAttemptStatusType.Success)
                    {
                        string error = "Failed to Connect to Reader";
                        throw new ApplicationException(error);
                    }
                }
                #endregion

                #region EnableExtensions
                {
                    MSG_IMPINJ_ENABLE_EXTENSIONS imp_msg = new MSG_IMPINJ_ENABLE_EXTENSIONS();
                    MSG_ERROR_MESSAGE msg_err;

                    MSG_CUSTOM_MESSAGE cust_rsp = reader.CUSTOM_MESSAGE(imp_msg, out msg_err, 8000);
                    MSG_IMPINJ_ENABLE_EXTENSIONS_RESPONSE msg_rsp = cust_rsp as MSG_IMPINJ_ENABLE_EXTENSIONS_RESPONSE;

                    if (msg_rsp != null)
                    {
                        if (msg_rsp.LLRPStatus.StatusCode != ENUM_StatusCode.M_Success)
                        {
                            string error = msg_rsp.LLRPStatus.StatusCode.ToString();
                            throw new ApplicationException(error);
                        }
                    }
                    else if (msg_err != null)
                    {
                        string error = msg_err.ToString();
                        throw new ApplicationException(error);
                    }
                    else
                    {
                        string error = "Enable Extensions Command Timed out";
                        throw new ApplicationException(error);
                    }
                }
                #endregion

                #region SetFactoryDefault
                {
                    MSG_SET_READER_CONFIG msg_cfg = new MSG_SET_READER_CONFIG();
                    MSG_ERROR_MESSAGE msg_err;

                    msg_cfg.ResetToFactoryDefault = true;

                    //if SET_READER_CONFIG affects antennas it could take several seconds to return
                    MSG_SET_READER_CONFIG_RESPONSE rsp_cfg = reader.SET_READER_CONFIG(msg_cfg, out msg_err, 12000);

                    if (rsp_cfg != null)
                    {
                        if (rsp_cfg.LLRPStatus.StatusCode != ENUM_StatusCode.M_Success)
                        {
                            string error = rsp_cfg.LLRPStatus.StatusCode.ToString();
                            throw new ApplicationException(error);
                        }
                    }
                    else if (msg_err != null)
                    {
                        string error = msg_err.ToString();
                        throw new ApplicationException(error);
                    }
                    else
                    {
                        string error = "Setting FactoryDefault Command Timed out";
                        throw new ApplicationException(error);
                    }
                }
                #endregion

                SetReaderConfig(searchType, reader, power, sensitivity);

                Add_RoSpec(reader);
                Enable_RoSpec(reader);
                Start_RoSpec(reader);
            }
            catch (ApplicationException aex)
            {
                MessageBox.Show(aex.Message);
                reader.Close();
            }
            catch (Exception Ex)
            {
                reader.Close();
                throw Ex;
            }
        }

        //int count = 0;
        private void reader_OnRoAccessReportReceived(MSG_RO_ACCESS_REPORT msg)
        {

            //Thread safe çalışması için ;
            context.Send(s =>
            {
                //Asıl yapılacak iş ;
                if (msg.TagReportData != null)
                {
                    for (int i = 0; i < msg.TagReportData.Length; i++)
                    {
                        PARAM_TagReportData data = msg.TagReportData[i];
                        ushort AntennaId = data.AntennaID.AntennaID;
                        sbyte RSSI = data.PeakRSSI.PeakRSSI;
                        double rssi = Convert.ToDouble(RSSI);
                        //int seenCount = Convert.ToInt32( data.TagSeenCount.TagCount);

                        object epcParameter = data.EPCParameter[0];

                        int epc;
                        if (epcParameter.GetType() == typeof(PARAM_EPC_96))
                            epc = ((PARAM_EPC_96)(epcParameter)).EPC.ToInt();
                        else
                            epc = ((PARAM_EPCData)(epcParameter)).EPC.ToInt();

                        if (epc != 0)
                            //DataAccess.InsertLog(epc, AntennaId, rssi, 0, 0, DateTime.Now);

                            TagManager.ProcessTag(epc, AntennaId, rssi);
                    }
                }
            }, null);
        }

        private void SetReaderConfig(ENUM_ImpinjInventorySearchType search, LLRPClient reader, ushort power, ushort sensitivity)
        {
            try
            {
                MSG_SET_READER_CONFIG msg = new MSG_SET_READER_CONFIG();

                msg.AntennaConfiguration = new PARAM_AntennaConfiguration[1];
                msg.AntennaConfiguration[0] = new PARAM_AntennaConfiguration();
                msg.AntennaConfiguration[0].AntennaID = 0;
                msg.AntennaConfiguration[0].AirProtocolInventoryCommandSettings = new UNION_AirProtocolInventoryCommandSettings();

                PARAM_C1G2InventoryCommand cmd = new PARAM_C1G2InventoryCommand();
                cmd.C1G2RFControl = new PARAM_C1G2RFControl();
                cmd.C1G2RFControl.ModeIndex = 1000;     //autoset
                cmd.C1G2RFControl.Tari = 0;

                cmd.C1G2SingulationControl = new PARAM_C1G2SingulationControl();
                cmd.C1G2SingulationControl.Session = new TwoBits(search == ENUM_ImpinjInventorySearchType.Dual_Target ? (ushort)2 : (ushort)1);
                cmd.C1G2SingulationControl.TagPopulation = 100; //bu değer nasıl?
                cmd.C1G2SingulationControl.TagTransitTime = 0;
                cmd.TagInventoryStateAware = false;

                PARAM_ImpinjInventorySearchMode search_mode = new PARAM_ImpinjInventorySearchMode();
                search_mode.InventorySearchMode = search;   //ENUM_ImpinjInventorySearchType.Single_Target_With_Suppression:ENUM_ImpinjInventorySearchType.Dual_Target;
                cmd.AddCustomParameter(search_mode);

                PARAM_ImpinjLowDutyCycle ldc = new PARAM_ImpinjLowDutyCycle();
                ldc.LowDutyCycleMode = ENUM_ImpinjLowDutyCycleMode.Enabled;
                ldc.EmptyFieldTimeout = (ushort)10000;
                ldc.FieldPingInterval = (ushort)200;
                cmd.AddCustomParameter(ldc);
                //Enable Low Duty Cycle when no tags are seen for 10 seconds.  Check antennas every 200 msec
                msg.AntennaConfiguration[0].AirProtocolInventoryCommandSettings.Add(cmd);

                msg.AntennaConfiguration[0].RFReceiver = new PARAM_RFReceiver();
                msg.AntennaConfiguration[0].RFReceiver.ReceiverSensitivity = sensitivity;

                msg.AntennaConfiguration[0].RFTransmitter = new PARAM_RFTransmitter();
                msg.AntennaConfiguration[0].RFTransmitter.ChannelIndex = 3;     //3'tü!
                msg.AntennaConfiguration[0].RFTransmitter.HopTableID = 0;
                msg.AntennaConfiguration[0].RFTransmitter.TransmitPower = power; //87;   // (ushort)txPwrIndx;

                msg.ROReportSpec = new PARAM_ROReportSpec();
                msg.ROReportSpec.N = 1;
                msg.ROReportSpec.ROReportTrigger = ENUM_ROReportTriggerType.Upon_N_Tags_Or_End_Of_ROSpec;
                msg.ROReportSpec.TagReportContentSelector = new PARAM_TagReportContentSelector();
                msg.ROReportSpec.TagReportContentSelector.AirProtocolEPCMemorySelector = new UNION_AirProtocolEPCMemorySelector();
                PARAM_C1G2EPCMemorySelector c1g2mem = new PARAM_C1G2EPCMemorySelector();
                c1g2mem.EnableCRC = false;
                c1g2mem.EnablePCBits = false;
                msg.ROReportSpec.TagReportContentSelector.AirProtocolEPCMemorySelector.Add(c1g2mem);

                msg.ROReportSpec.TagReportContentSelector.EnableAccessSpecID = true;
                msg.ROReportSpec.TagReportContentSelector.EnableAntennaID = true;
                msg.ROReportSpec.TagReportContentSelector.EnableChannelIndex = true;
                msg.ROReportSpec.TagReportContentSelector.EnableFirstSeenTimestamp = true;
                msg.ROReportSpec.TagReportContentSelector.EnableInventoryParameterSpecID = true;
                msg.ROReportSpec.TagReportContentSelector.EnableLastSeenTimestamp = true;
                msg.ROReportSpec.TagReportContentSelector.EnablePeakRSSI = true;
                msg.ROReportSpec.TagReportContentSelector.EnableROSpecID = true;
                msg.ROReportSpec.TagReportContentSelector.EnableSpecIndex = true;
                msg.ROReportSpec.TagReportContentSelector.EnableTagSeenCount = true;

                msg.ResetToFactoryDefault = false;

                MSG_ERROR_MESSAGE msg_err;
                MSG_SET_READER_CONFIG_RESPONSE rsp = reader.SET_READER_CONFIG(msg, out msg_err, 3000);

                if (rsp != null)
                {
                    if (rsp.LLRPStatus.StatusCode != ENUM_StatusCode.M_Success)
                    {
                        string error = rsp.ToString();
                        throw new ApplicationException(error);
                    }
                }
                else if (msg_err != null)
                {
                    string error = msg_err.ToString();
                    throw new ApplicationException(error);
                }
                else
                {
                    string error = "setReaderConfig Timed out";
                    throw new ApplicationException(error);
                }
            }
            catch (ApplicationException aex)
            {
                MessageBox.Show(aex.Message);
                reader.Close();
            }
            catch (Exception Ex)
            {
                reader.Close();
                throw Ex;
            }
        }

        public void StopReader()
        {
            List<LLRPClient> stoppedReaders = new List<LLRPClient>();
            try
            {
                foreach (LLRPClient reader in readers)
                {
                    reader.OnRoAccessReportReceived -= new delegateRoAccessReport(reader_OnRoAccessReportReceived);
                    //reader.OnReaderEventNotification -= new delegateReaderEventNotification(reader_OnReaderEventNotification);
                    Stop_RoSpec(reader);
                    Delete_RoSpec(reader);
                    reader.Close();
                    stoppedReaders.Add(reader);
                }
            }
            catch (ApplicationException aex)
            {
                MessageBox.Show(aex.ToString());
            }
            catch (Exception Ex)
            {
                throw Ex;
            }
            finally
            {
                foreach (LLRPClient client in stoppedReaders)
                {
                    readers.Remove(client);
                }
            }
        }

        private void Add_RoSpec(LLRPClient reader)
        {
            MSG_ADD_ROSPEC msg = new MSG_ADD_ROSPEC();
            msg.ROSpec = new PARAM_ROSpec();
            msg.ROSpec.CurrentState = ENUM_ROSpecState.Disabled;
            msg.ROSpec.Priority = 0x00;
            msg.ROSpec.ROSpecID = 1034;

            msg.ROSpec.ROBoundarySpec = new PARAM_ROBoundarySpec();
            msg.ROSpec.ROBoundarySpec.ROSpecStartTrigger = new PARAM_ROSpecStartTrigger();
            msg.ROSpec.ROBoundarySpec.ROSpecStartTrigger.ROSpecStartTriggerType = ENUM_ROSpecStartTriggerType.Null;
            msg.ROSpec.ROBoundarySpec.ROSpecStopTrigger = new PARAM_ROSpecStopTrigger();
            msg.ROSpec.ROBoundarySpec.ROSpecStopTrigger.ROSpecStopTriggerType = ENUM_ROSpecStopTriggerType.Null;
            msg.ROSpec.ROBoundarySpec.ROSpecStopTrigger.DurationTriggerValue = 0;

            PARAM_AISpec aiSpec = new PARAM_AISpec();
            aiSpec.AntennaIDs = new UInt16Array();
            aiSpec.AntennaIDs.Add(0);   //applies to all antennas

            aiSpec.AISpecStopTrigger = new PARAM_AISpecStopTrigger();
            aiSpec.AISpecStopTrigger.AISpecStopTriggerType = ENUM_AISpecStopTriggerType.Null;
            aiSpec.AISpecStopTrigger.DurationTrigger = 0;

            aiSpec.InventoryParameterSpec = new PARAM_InventoryParameterSpec[1];
            aiSpec.InventoryParameterSpec[0] = new PARAM_InventoryParameterSpec();
            aiSpec.InventoryParameterSpec[0].InventoryParameterSpecID = 1234;
            aiSpec.InventoryParameterSpec[0].ProtocolID = ENUM_AirProtocols.EPCGlobalClass1Gen2;

            msg.ROSpec.SpecParameter.Add(aiSpec);

            MSG_ERROR_MESSAGE msg_err;
            MSG_ADD_ROSPEC_RESPONSE rsp = reader.ADD_ROSPEC(msg, out msg_err, 12000);

            if (rsp != null)
            {
                if (rsp.LLRPStatus.StatusCode != ENUM_StatusCode.M_Success)
                {
                    string error = rsp.ToString();
                    throw new ApplicationException(error);
                }
            }
            else if (msg_err != null)
            {
                string error = msg_err.ToString();
                throw new ApplicationException(error);
            }
            else
            {
                string error = "ADD_ROSPEC Command Timed out";
                throw new ApplicationException(error);
            }
        }

        private void Delete_RoSpec(LLRPClient reader)
        {
            MSG_DELETE_ROSPEC msg = new MSG_DELETE_ROSPEC();
            MSG_ERROR_MESSAGE msg_err;
            msg.ROSpecID = 1034;   // Deletes ROSpec for the given id
            MSG_DELETE_ROSPEC_RESPONSE rsp = reader.DELETE_ROSPEC(msg, out msg_err, 3000);

            if (rsp != null)
            {
                if (rsp.LLRPStatus.StatusCode != ENUM_StatusCode.M_Success)
                {
                    string error = rsp.ToString();
                    throw new ApplicationException(error);
                }
            }
            else if (msg_err != null)
            {
                string error = msg_err.ToString();
                throw new ApplicationException(error);
            }
            else
            {
                string error = "DELETE_ROSPEC Command Timed out";
                throw new ApplicationException(error);
            }
        }

        private void Enable_RoSpec(LLRPClient reader)
        {
            MSG_ENABLE_ROSPEC msg = new MSG_ENABLE_ROSPEC();
            MSG_ERROR_MESSAGE msg_err;
            msg.ROSpecID = 1034; // this better match the ROSpec we created above
            MSG_ENABLE_ROSPEC_RESPONSE rsp = reader.ENABLE_ROSPEC(msg, out msg_err, 12000);

            if (rsp != null)
            {
                if (rsp.LLRPStatus.StatusCode != ENUM_StatusCode.M_Success)
                {
                    string error = rsp.LLRPStatus.StatusCode.ToString();
                    throw new ApplicationException(error);
                }
            }
            else if (msg_err != null)
            {
                string error = msg_err.ToString();
                throw new ApplicationException(error);
            }
            else
            {
                string error = "Enable_RoSpec Command Timed out";
                throw new ApplicationException(error);
            }
        }

        private void Start_RoSpec(LLRPClient reader)
        {
            MSG_START_ROSPEC msg = new MSG_START_ROSPEC();
            MSG_ERROR_MESSAGE msg_err;
            msg.ROSpecID = 1034; // this better match the RoSpec we created above
            MSG_START_ROSPEC_RESPONSE rsp = reader.START_ROSPEC(msg, out msg_err, 12000);

            if (rsp != null)
            {
                if (rsp.LLRPStatus.StatusCode != ENUM_StatusCode.M_Success)
                {
                    string error = rsp.LLRPStatus.StatusCode.ToString();
                    throw new ApplicationException(error);
                }
            }
            else if (msg_err != null)
            {
                string error = msg_err.ToString();
                throw new ApplicationException(error);
            }
            else
            {
                string error = "Start_RoSpec Command Timed out";
                throw new ApplicationException(error);
            }
        }

        private void Stop_RoSpec(LLRPClient reader)
        {
            MSG_STOP_ROSPEC msg = new MSG_STOP_ROSPEC();
            MSG_ERROR_MESSAGE msg_err;
            msg.ROSpecID = 1034; // this better match the RoSpec we created above
            MSG_STOP_ROSPEC_RESPONSE rsp = reader.STOP_ROSPEC(msg, out msg_err, 12000);

            if (rsp != null)
            {
                if (rsp.LLRPStatus.StatusCode != ENUM_StatusCode.M_Success)
                {
                    string error = rsp.LLRPStatus.StatusCode.ToString();
                    throw new ApplicationException(error);
                }
            }
            else if (msg_err != null)
            {
                string error = msg_err.ToString();
                throw new ApplicationException(error);
            }
            else
            {
                string error = "Stop_RoSpec Command Timed out";
                throw new ApplicationException(error);
            }
        }
    }
}

namespace Extensions
{
    public static class LLRPExtensions
    {
        public static int ToInt(this LLRPBitArray epc)
        {
            string epcString = epc.ToHexString().TrimStart('0');

            int result = -1;
            int.TryParse(epcString.ToString(), out result);
            return result;
        }
    }
}